/*
 * ReadOnlyDataConnection.java
 * 
 * Created by demory on Aug 8, 2009, 10:00:17 PM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.core.data;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.fpdev.core.FPEngine;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BNode;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.filter.FidFilter;
import org.geotools.filter.FilterFactory;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;

/**
 *
 * @author demory
 */
public abstract class QueryableDataConnection implements SpatialDataConnection {

  protected FPEngine engine_;

  //protected DataStore dataStore_;
  protected FeatureStore nodeFeatStore_, linkFeatStore_;

  private Map<Integer, String> nodesFIDLookup_, linksFIDLookup_;

  protected GeometryFactory gf_;
  protected FilterFactory ff_;
  protected FilterFactory2 ff2_;

  public QueryableDataConnection(FPEngine engine) {
    engine_ = engine;
    gf_ = new GeometryFactory();
    ff_ = (FilterFactory) CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints());
    ff2_ = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
  }

  public abstract String getGeomFieldName();

  public FeatureSource getNodeSource() {
    return nodeFeatStore_;
  }

  public FeatureSource getLinkSource() {
    return linkFeatStore_;
  }

  public void buildFIDLookup() {
    nodesFIDLookup_ = new HashMap<Integer, String>();
    linksFIDLookup_ = new HashMap<Integer, String>();
    try {

      FeatureCollection coll = nodeFeatStore_.getFeatures();
      System.out.println("building nodes lookup..");
      for(FeatureIterator iter = coll.features(); iter.hasNext(); ) {
        SimpleFeature f = (SimpleFeature) iter.next();
        int nodeID = (Integer) f.getProperty("fpid").getValue();
        nodesFIDLookup_.put(nodeID, f.getID());
      }
      System.out.println("done");

      coll = linkFeatStore_.getFeatures();
      System.out.println("building links lookup..");
      for(FeatureIterator iter = coll.features(); iter.hasNext(); ) {
        SimpleFeature f = (SimpleFeature) iter.next();
        int linkID = (Integer) f.getProperty("fpid").getValue();
        linksFIDLookup_.put(linkID, f.getID());
      }
      System.out.println("done");

    } catch(Exception e) {
      e.printStackTrace();
    }
  }

  public BNode nodeFromID(int id) {
    //Filter filter = ff2_.equals(ff2_.property("fpid"), ff2_.literal(id));
    //System.out.println("node from id");
    FidFilter filter = ff_.createFidFilter();//  2_.featureId(nodesFIDLookup_.get(id));
    filter.addFid(nodesFIDLookup_.get(id));
    return getNodes(filter).iterator().next();
  }

  public Collection<BNode> allNodes() {
    return getNodes(null);          
  }
  
  public Collection<BNode> nearbyNodes(double x, double y, double r) {
    Geometry pt = gf_.createPoint(new Coordinate(x,y));
    Filter filter = ff2_.dwithin(ff2_.property(getGeomFieldName()), ff2_.literal(pt), r, "ft");
    return getNodes(filter);      
  }
  
  public Collection<BNode> nodesInRange(double x1, double y1, double x2, double y2) {
    Filter filter = ff2_.bbox(ff2_.property(getGeomFieldName()), Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2), engine_.getDataPackage().getProperty("crsCode"));
    return getNodes(filter);
  }
  
  public Collection<BNode> nodesInSet(Collection<Integer> ids) {
    /*PropertyName fpid = ff_.property("fpid");
    List<Filter> filters = new LinkedList<Filter>();
    for(Integer id : ids) {
      //System.out.println(" id="+id);
      if(engine_.getNodeFromID(id) == null)
        filters.add(ff_.equals(fpid, ff_.literal(id)));
    }
    return getNodes(ff_.or(filters));*/
    //System.out.println("nodes in set");
    FidFilter filter = ff_.createFidFilter();
    for(Integer id : ids)
      filter.addFid(nodesFIDLookup_.get(id));

    return getNodes(filter);

  }

  private Collection<BNode> getNodes(Filter filter) {
    Set<BNode> nodes = new HashSet<BNode>();
    try {

      FeatureCollection coll = (filter != null) ? 
        nodeFeatStore_.getFeatures(filter) : nodeFeatStore_.getFeatures();
      
      for(FeatureIterator iter = coll.features(); iter.hasNext(); ) {
        SimpleFeature f = (SimpleFeature) iter.next();
        int nodeID = (Integer) f.getProperty("fpid").getValue();
        if(engine_.getNodeFromID(nodeID) == null) {
          BNode node = engine_.getDataPackage().getFeatureIO().readNode(f);
          engine_.getBaseNet().addNode(node);
          nodes.add(node);
        }
        else nodes.add(engine_.getNodeFromID(nodeID));
      }

    } catch(Exception e) {
      e.printStackTrace();
    }
    return nodes;
  }


  // Link Query Methods

  public Collection<BLink> allLinks() {
    return getLinks(null);
  }

  public Collection<BLink> linksInRange(double x1, double y1, double x2, double y2) {
    Filter filter = ff_.bbox(getGeomFieldName(), Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2), engine_.getDataPackage().getProperty("crsCode"));
    return getLinks(filter);
  }

  public Collection<BLink> linksInSet(Collection<Integer> ids) {

    FidFilter filter = ff_.createFidFilter();
    for(Integer id : ids)
      filter.addFid(linksFIDLookup_.get(id));

    return getLinks(filter);
  }

  
  private Collection<BLink> getLinks(Filter filter) {
    Set<BLink> links = new HashSet<BLink>();
    try {

      System.out.println(" getting link coll..");
      FeatureCollection coll = (filter != null) ?
        linkFeatStore_.getFeatures(filter) : linkFeatStore_.getFeatures();
      System.out.println(" done");


      Set<Integer> nodesToLoad = new HashSet<Integer>();

      for(FeatureIterator iter = coll.features(); iter.hasNext(); ) {
        SimpleFeature f = (SimpleFeature) iter.next();
        int fNodeID = (Integer) f.getProperty("fnode").getValue();
        int tNodeID = (Integer) f.getProperty("tnode").getValue();
        if(engine_.getNodeFromID(fNodeID) == null) nodesToLoad.add(fNodeID);
        if(engine_.getNodeFromID(tNodeID) == null) nodesToLoad.add(tNodeID);
      }
      System.out.println("  loading subnodes..");
      this.nodesInSet(nodesToLoad);
      System.out.println("  done");

      //int l = 0;
      for(FeatureIterator iter = coll.features(); iter.hasNext(); ) {
        SimpleFeature f = (SimpleFeature) iter.next();
        int linkID = (Integer) f.getProperty("fpid").getValue();
        if(engine_.getLinkFromID(linkID) == null) {
          BLink link = engine_.getDataPackage().getFeatureIO().readLink(f);
          if(link != null) {
            engine_.getBaseNet().addLink(link);
            links.add(link);
            //l++;
          }
        }
        //else links.add(engine_.getLinkFromID(linkID));

        //if(l % 100 == 0) System.out.println("l="+l);
        //l++;
      }
      //System.out.println("init: "+l);

    } catch(Exception e) {
      e.printStackTrace();
    }
    return links;
  }
}
